/* MIT License
 *
 * Copyright (c) 2012 Reuben Smith, Miami University
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.droidlike.vsig;


import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.ui.Picture;


/**
 * Tool.
 * 
 * @author smithre5
 */
class Tool
{
    /**
     * Creates a tool for the given simulation. Though there should only be one.
     * 
     * @param sim the associated sim
     * @return a ready Tool if successful; null otherwise
     */
    public static Tool create(Simulation sim)
    {
        if (sim == null) {
            return null;
        }
        
        Tool tool = new Tool(sim);
        tool.currentTool = ToolType.Select;
        tool.createUI();
        tool.placeUI();
        tool.addInputs();
                
        return tool;
    }
    
    
    /**
     * Attaches the UI elements of the tool to the GUI.
     */
    public void attachUI()
    {
        uiScene.attachChild(uiBorder);
        uiScene.attachChild(uiIcons[currentTool.ordinal()]);
    }
    
    
    /**
     * Detaches the tool's UI elements from the GUI.
     */
    public void detachUI()
    {
        uiScene.detachChild(uiBorder);
        uiScene.detachChild(uiIcons[currentTool.ordinal()]);
    }
    
    
    /**
     * Updates the positions of UI elements. Primarily for use after the canvas
     * size has changed.
     */
    public void placeUI()
    {
        uiBorder.setPosition(
            sim.getCamera().getWidth() - UI_BORDER_SIZE.x - UI_MARGIN.x,
            UI_MARGIN.y
        );
        
        Vector2f borderOffset = new Vector2f(
            0.5f * (UI_BORDER_SIZE.x - UI_ICON_SIZE.x),
            0.5f * (UI_BORDER_SIZE.y - UI_ICON_SIZE.y)
        );
        
        for (int i = uiIcons.length; --i >= 0;) {
            uiIcons[i].setPosition(
                uiBorder.getLocalTranslation().x + borderOffset.x,
                UI_MARGIN.y + borderOffset.y
            );
        }
    }
    
    
    /**
     * Default constructor.
     * 
     * @param sim the associated sim
     */
    private Tool(Simulation sim)
    {
        this.sim = sim;
        this.uiScene = sim.getScene2D();
    }
    
    
    /**
     * Maps the tool's inputs and adds a listener for them.
     */
    private void addInputs()
    {
        // smithre5: This is more of a procedural approach to handling input. Hm.
        
        if (sim == null) {
            throw new IllegalStateException("addInputs given null InputManager");
        }
        
        Profile profile = VSIG.getProfile();
        InputManager input = sim.getInputManager();
        
        if (VSIG.DEBUG) {
            input.addMapping("DebugVehicle", new KeyTrigger(
                Integer.parseInt(profile.getValue("input.debug_vehicle"))));
        }
        
        input.addMapping("UseToolSelect", new KeyTrigger(
            Integer.parseInt(profile.getValue("input.usetool_select"))));
        input.addMapping("UseToolPlaceSignal", new KeyTrigger(
            Integer.parseInt(profile.getValue("input.usetool_signal"))));
        input.addMapping("UseToolPlaceSensor", new KeyTrigger(
            Integer.parseInt(profile.getValue("input.usetool_sensor"))));
        input.addMapping("UseToolPlaceControl", new KeyTrigger(
            Integer.parseInt(profile.getValue("input.usetool_control"))));
        input.addMapping("ToolPrimary", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        input.addMapping("ToolSecondary", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
        
        input.addListener(new ActionListener() {
            public void onAction(String name, boolean pressed, float tpf)
            {
                Tool tool = Tool.this;
                
                if (name.startsWith("UseTool") && !pressed) {
                    tool.detachUI();
                    tool.currentTool = ToolType.valueOf(name.substring(7));
                    tool.placeUI();
                    tool.attachUI();
                }
                else if (name.equals("ToolPrimary") && !pressed) {
                    CollisionResult selection = getSelectionResult();
                    if (selection == null) {
                        return;
                    }
                    
                    switch (tool.currentTool) {
                        case Select:
                            handleSelect(selection);
                            break;
                            
                        case PlaceSignal:
                            handlePlaceSignal(selection);
                            break;
                            
                        case PlaceSensor:
                            handlePlaceSensor(selection);
                            break;
                            
                        case PlaceControl:
                            handlePlaceControl(selection);
                            break;
                            
                        default:
                            break;
                    }
                }
                else if (name.equals("ToolSecondary")) {
                    // STUB
                    System.out.println("ToolSecondary");
                }
                else if (VSIG.getDebugMode() && name.equals("DebugVehicle") && !pressed) {
                    CollisionResult selection = getSelectionResult();
                    if (selection == null) {
                        return;
                    }
                    
                    sim.createAgent(selection.getContactPoint());
                }
            }
        }, new String[] {
            "DebugVehicle",
            "UseToolSelect",
            "UseToolPlaceSignal",
            "UseToolPlaceSensor",
            "UseToolPlaceControl",
            "ToolPrimary",
            "ToolSecondary"
        });
    }
    
    
    /**
     * Creates UI elements.
     */
    private void createUI()
    {
        if (sim == null) {
            throw new IllegalStateException("createUI given null AssetManager");
        }
        
        AssetManager assets = sim.getAssetManager();
        
        uiBorder = new Picture("ui-tool-border");
        uiBorder.setImage(assets, "Interface/tool_window.png", true);
        uiBorder.setWidth(UI_BORDER_SIZE.x);
        uiBorder.setHeight(UI_BORDER_SIZE.y);
        
        uiIcons = new Picture[4];
        uiIcons[0] = new Picture("ui-tool-signal");
        uiIcons[0].setImage(assets, "Interface/tool_signal.png", true);
        uiIcons[0].setWidth(UI_ICON_SIZE.x);
        uiIcons[0].setHeight(UI_ICON_SIZE.y);
        
        uiIcons[1] = new Picture("ui-tool-sensor");
        uiIcons[1].setImage(assets, "Interface/tool_sensor.png", true);
        uiIcons[1].setWidth(UI_ICON_SIZE.x);
        uiIcons[1].setHeight(UI_ICON_SIZE.y);
        
        uiIcons[2] = new Picture("ui-tool-control");
        uiIcons[2].setImage(assets, "Interface/tool_control.png", true);
        uiIcons[2].setWidth(UI_ICON_SIZE.x);
        uiIcons[2].setHeight(UI_ICON_SIZE.y);
        
        uiIcons[3] = new Picture("ui-tool-select");
        uiIcons[3].setImage(assets, "Interface/tool_select.png", true);
        uiIcons[3].setWidth(UI_ICON_SIZE.x);
        uiIcons[3].setHeight(UI_ICON_SIZE.y);
    }
    
    
    /**
     * Returns a point in the 3D scene from the mouse.
     * 
     * @return the nearest selection point if there was one; null otherwise
     */
    private CollisionResult getSelectionResult()
    {
        CollisionResults collisions = new CollisionResults();
                    
        Vector2f mousePos2D = sim.getInputManager().getCursorPosition();
        Vector3f mousePos3D = sim.getCamera().getWorldCoordinates(mousePos2D, 0f).clone();
        Vector3f mouseDir3D = sim.getCamera().getWorldCoordinates(mousePos2D, 1f).clone()
            .subtractLocal(mousePos3D).normalizeLocal();
                    
        Ray trace = new Ray(mousePos3D, mouseDir3D);
        sim.getScene3D().collideWith(trace, collisions);
                    
        if (collisions.size() > 0) {
            return collisions.getClosestCollision();
        }
        
        return null;
    }
    
    
    /**
     * Perform logic specific to the select tool.
     * 
     * @param selection the selection data
     */
    private void handleSelect(CollisionResult selection)
    {
        InfoTableModel info = (InfoTableModel)sim.getInfoTable().getModel();
        info.clear();
        
        RoadNode node = sim.getGraph().getNearestTo(selection.getContactPoint(), 15.0f);
        if (node == null) {
            return;
        }
        
        info.setInfo(node.getTableInfo(sim.getGraph()));
    }
    
    
    /**
     * Perform logic specific to the place-signal tool.
     * 
     * @param selection the selection data
     */
    private void handlePlaceSignal(CollisionResult selection)
    {
        RoadNode node = sim.getGraph().getNearestTo(selection.getContactPoint(), 15.0f);
        if (node == null) {
            return;
        }
        
        Vector3f placement = node.getLanePosition(node.getLanes()).multLocal(1.25f);
        TrafficSignal signal = TrafficSignal.create(sim.getExecutorService(), sim.getAssetManager());
        signal.spatial.lookAt(node.getDirection().negateLocal(), node.getUp());
        
        if (sim.bindSignal(node, signal)) {
            signal.attachAt(sim.getScene3D(), placement.addLocal(0, 5f, 0));
            System.out.println("Bound signal.");
        }
    }
    
    
    /**
     * Perform logic specific to the place-sensor tool.
     * 
     * @param selection the selection data
     */
    private void handlePlaceSensor(CollisionResult selection)
    {
        
    }
    
    
    /**
     * Perform logic specific to the place-control tool.
     * 
     * @param selection the selection data
     */
    private void handlePlaceControl(CollisionResult selection)
    {
        
    }
    
    
    private enum ToolType
    {
        PlaceSignal,
        PlaceSensor,
        PlaceControl,
        Select
    }
    
    private static final Vector2f UI_MARGIN = new Vector2f(5, 5);
    private static final Vector2f UI_BORDER_SIZE = new Vector2f(36, 36);
    private static final Vector2f UI_ICON_SIZE = new Vector2f(32, 32);
    
    private Simulation sim;
    
    private ToolType currentTool;
    private Node uiScene;
    private Picture uiBorder;
    private Picture[] uiIcons;
}